package com.skaz.util;

import com.skaz.Constants;
import org.hibernate.service.spi.ServiceException;

import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;

public abstract class Exceptions {

    public static final String EXCEPTION_CLASS_SECURITY_SPRING_AccessDeniedException = "org.springframework.security.access.AccessDeniedException";
    public static final String EXCEPTION_CLASS_SECURITY_SPRING_AuthenticationException = "org.springframework.security.core.AuthenticationException";
    public static final String EXCEPTION_CLASS_SECURITY_SPRING_AccountExpiredException = "org.springframework.security.authentication.AccountExpiredException";
    public static final String EXCEPTION_CLASS_SECURITY_SHIRO_AuthorizationException = "org.apache.shiro.authz.AuthorizationException";
    public static final String EXCEPTION_CLASS_SECURITY_SHIRO_UnauthorizedException = "org.apache.shiro.authz.UnauthorizedException";
    public static final String EXCEPTION_CLASS_SECURITY_SHIRO_UnauthenticatedException = "org.apache.shiro.authz.UnauthenticatedException";

    private static final String[] SECURITY_SPRING_EXCEPTIONS = {
            EXCEPTION_CLASS_SECURITY_SPRING_AccessDeniedException,
            EXCEPTION_CLASS_SECURITY_SPRING_AuthenticationException,
            EXCEPTION_CLASS_SECURITY_SPRING_AccountExpiredException
    };

    private static final String[] SECURITY_SHIRO_EXCEPTIONS = {
            EXCEPTION_CLASS_SECURITY_SHIRO_AuthorizationException,
            EXCEPTION_CLASS_SECURITY_SHIRO_UnauthorizedException,
            EXCEPTION_CLASS_SECURITY_SHIRO_UnauthenticatedException
    };

    /**
     * 将CheckedException转换为UncheckedException.
     */
    public static RuntimeException unchecked(Throwable ex) {
        if (ex instanceof RuntimeException) {
            return (RuntimeException) ex;
        } else {
            return new RuntimeException(ex);
        }
    }

    public static RuntimeException newException(Throwable ex) {
        return new RuntimeException(ex);
    }

    public static RuntimeException newException(Throwable ex, String msg) {
        return new RuntimeException(msg, ex);
    }

    public static RuntimeException newRuntimeException(String msg) {
        return new RuntimeException(msg);
    }

    public static RuntimeException newRuntimeException(Throwable ex) {
        return new RuntimeException(ex.getMessage(), ex);
    }

    public static RuntimeException newRuntimeException(Throwable ex, String msg) {
        return new RuntimeException(msg, ex);
    }

    public static RuntimeException newRuntimeException(String msg, Throwable ex) {
        return new RuntimeException(msg, ex);
    }

    public static boolean isSecurityException(Throwable ex) {
        return isSecuritySpringException(ex) || isSecurityShiroException(ex);
    }

    public static boolean isSecuritySpringException(Throwable ex) {
        return isException(ex, SECURITY_SPRING_EXCEPTIONS);
    }

    public static boolean isSecurityShiroException(Throwable ex) {
        return isException(ex, SECURITY_SHIRO_EXCEPTIONS);
    }

    public static boolean isException(Throwable throwable, String... exceptions) {
        for (String exception : exceptions) {
            if (getExceptionClassName(throwable).equals(exception)) {
                return true;
            }
        }
        return false;
    }


    public static boolean isException(Throwable throwable, String[]... exceptions) {
        for (String[] exception : exceptions) {
            if (isException(throwable, exception)) {
                return true;
            }
        }
        return false;
    }

    public static Integer determineResponseStatus(Throwable ex) {
        if (ex instanceof ServiceException) {
            return HttpServletResponse.SC_OK;
        } else if (isException(ex, EXCEPTION_CLASS_SECURITY_SPRING_AuthenticationException)) {
            return Constants.ERROR_STATUS_401;
        } else if (isException(ex, EXCEPTION_CLASS_SECURITY_SPRING_AccountExpiredException)) {
            return Constants.ERROR_STATUS_401;
        } else if (isException(ex, SECURITY_SPRING_EXCEPTIONS, SECURITY_SHIRO_EXCEPTIONS)) {
            return Constants.ERROR_STATUS_403;
        }
        return Constants.ERROR_STATUS_500;
    }

    public static String getExceptionClassName(Throwable ex) {
        return getRootCause(ex).getClass().getName();
    }

    /**
     * 获取异常的Root Cause.
     */
    public static Throwable getRootCause(Throwable ex) {
        Throwable cause;
        while ((cause = ex.getCause()) != null) {
            ex = cause;
        }
        return ex;
    }

    /**
     * 获取异常的Root Message.
     */
    public static String getRootMessage(Throwable ex) {
        return getRootCause(ex).getMessage();
    }

    /**
     * 判断异常是否由某些底层的异常引起.
     */
    @SuppressWarnings("unchecked")
    public static boolean isCausedBy(Exception ex, Class<? extends Exception>... causeExceptionClasses) {
        Throwable cause = ex;
        while (cause != null) {
            for (Class<? extends Exception> causeClass : causeExceptionClasses) {
                if (causeClass.isInstance(cause)) {
                    return true;
                }
            }
            cause = cause.getCause();
        }
        return false;
    }

    /**
     * 将ErrorStack转化为String.
     */
    public static String getStackTraceAsString(Throwable ex) {
        StringWriter stringWriter = new StringWriter();
        ex.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

    /**
     * 获取组合本异常信息与底层异常信息的异常描述, 适用于本异常为统一包装异常类，底层异常才是根本原因的情况。
     */
    public static String getErrorMessageWithNestedException(Throwable ex) {
        Throwable nestedException = ex.getCause();
        return new StringBuilder().append(ex.getMessage()).append(" nested exception is ").append(nestedException.getClass().getName()).append(":").append(nestedException.getMessage()).toString();
    }


    public static String determineResponseMessage(Exception ex) {
        if (ex instanceof ServiceException) {
            return ex.getMessage();
        } else if (isException(ex, EXCEPTION_CLASS_SECURITY_SPRING_AuthenticationException)) {
            return Constants.ERROR_STATUS_401_MSG;
        } else if (isException(ex, EXCEPTION_CLASS_SECURITY_SPRING_AccountExpiredException)) {
            return Constants.ERROR_STATUS_401_EXPIRED_MSG;
        } else if (isException(ex, SECURITY_SPRING_EXCEPTIONS, SECURITY_SHIRO_EXCEPTIONS)) {
            return Constants.ERROR_STATUS_403_MSG;
        }
        return Constants.ERROR_STATUS_500_MSG;
    }
}
